Activations.php

<?php

namespace Tlf\User;

trait Activations {

    /**
     * Check that an activation code is valid. If a code has more than 1 entry in the DB, all entries using that code are deleted. If a code has no valid user attached to it, it will be deleted.
     * 
     * 
     * @param string $code an activation code generated by sentinel
     * @return bool true if activation code is valid. False otherwise
     */
    public function checkActivationCode($code){
        //@TODO Guarantee uniqueness of activation codes. Sentinel does NOT do this, but I do check for duplication in this script. There's issues with my current approach, as it could potentially delete somebody else's activation code, thus requiring them to reset their password
        $core = $this;
        $pdo = $core->getPdo();
        $stmt = $pdo->prepare("SELECT user_id, completed, UNIX_TIMESTAMP(created_at) AS created_at, UNIX_TIMESTAMP(CURRENT_TIMESTAMP()) AS now FROM activations WHERE `code` LIKE :code");
        $stmt->execute([':code'=>$code]);
        $rows = $stmt->fetchAll();
        $numRows = count($rows);
        
        $deleteActivation = function() use ($code, $pdo){
            $del = $pdo->prepare("DELETE FROM activations WHERE `code` LIKE :code");
            $del->execute([':code'=>$code]);
        };
        // I might combine >1 & ==0 into a single message that says "There was a problem with your activation code. Reset password"
        if ($numRows>=2||$numRows==0){
            if ($numRows>=2)$deleteActivation();
            return false;
        }
        $row = $rows[0];
        $userId = $row['user_id'];
        $didComplete = (bool)$row['completed'];
        $createdAt = (int)$row['created_at'];
        // $expiration = $createdAt + 60*60*1; //60 secs * 60 mins * 1 hour = 1 hour
        // $expiration = $createdAt + 15;
        $now = (int)$row['now'];
        //@TODO Implement proper expiration checking. Sentinel seems to be creating activation codes in the future, so expiration checks cannot be counted on at this time.
        $expiration = $now + 100;
        if ($didComplete||$now>$expiration){
            return false;
        }
        $user = $this->backend->userById($userId);
        if ($user==null){
            $deleteActivation();
            return false;
        }
        return true;
    }
    
    public function newActivationCode($user){
        $model = $user->model();
        if ($model==null)return null;//@TODO should I throw an exception here?
        $code = $this->backend->newActivationCode($model);
        return $code;
    }
    public function completeActivation($user, $code){
        return $this->backend->completeActivation($user->model(), $code);
    }
    /**
     * 
     * @param unknown $code
     * @return boolean|NULL|array `false` if there are multiple entries for the activation code. `null` if no entries. an array of the row if found.
     */
    public function activationDetails($code){
        $pdo = $this->getPdo();
        $stmt = $pdo->prepare("SELECT * FROM activations WHERE `code` LIKE :code");
        $stmt->execute([':code'=>$code]);
        $rows = $stmt->fetchAll();
        if (count($rows)>1){
            //@TODO log the error? Or maybe delete the duplicates? cause now we have an issue
            return false;
//             throw new \Exception("There are multiple entries for the same activation code.");
        } else if (count($rows)==0){
            return null;
        }
        return $rows[0];
    }
}